﻿using System;
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MugenInjection;
using MugenInjection.Bindings;
using MugenInjection.Bindings.Builders;
using MugenInjection.Exceptions;
using MugenInjection.Syntax;
using Should;

namespace MugenInjectionTest.Bindings
{
    [TestClass]
    public class BindingBuilderToTest : TestBase
    {
        [TestMethod]
        public void ToTypeTest()
        {
            using (var injector = GetInjector())
            {
                IBindingSyntax bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(I1) }, injector);
                bindingBuilder.To(typeof(Class));
                bindingBuilder.Build();
            }
        }

        [TestMethod]
        public void ToTypeNotAssignableTest()
        {
            using (var injector = GetInjector())
            {
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(I1) }, injector);
                Action action = () => bindingBuilder.To(typeof(Class2));
                action.ShouldThrow<ArgumentException>();
            }
        }

        [TestMethod]
        public void ToTypeNullTest()
        {
            using (var injector = GetInjector())
            {
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(I1) }, injector);
                Action action = () => bindingBuilder.To(typeTo: null);
                action.ShouldThrow<ArgumentNullException>();
            }
        }

        [TestMethod]
        public void ToTypeNotBindableInterfaceTest()
        {
            using (var injector = GetInjector())
            {
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(I1) }, injector);
                Action action = () => bindingBuilder.To(typeof(I2));
                action.ShouldThrow<InvalidBindingException>();
            }
        }

        [TestMethod]
        public void ToTypeNotBindableAbstractClassTest()
        {
            using (var injector = GetInjector())
            {
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(I1) }, injector);
                Action action = () => bindingBuilder.To(typeof(AbstractClass));
                action.ShouldThrow<InvalidBindingException>();
            }
        }

        [TestMethod]
        public void ToTypeNotBindableOpenGenericClassTest()
        {
            using (var injector = GetInjector())
            {
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(I1) }, injector);
                Action action = () => bindingBuilder.To(typeof(GenericClass<>));
                action.ShouldThrow<InvalidBindingException>();

                bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(I3<>) }, injector);
                action = () => bindingBuilder.To(typeof(GenericClass2<string>));
                action.ShouldThrow<ArgumentException>();
            }
        }

        [TestMethod]
        public void ToTypeBindableOpenGenericClassTest()
        {
            using (var injector = GetInjector())
            {
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(I3<>) }, injector);
                bindingBuilder.To(typeof(GenericClass2<>));
                bindingBuilder.Build();

                bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(I3<object>) }, injector);
                bindingBuilder.To(typeof(GenericClass2<object>));
                bindingBuilder.Build();
            }
        }

        [TestMethod]
        public void ToTypeValueTypeTest()
        {
            using (var injector = GetInjector())
            {
                //Value type with constructor
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(I1) }, injector);
                bindingBuilder.To(typeof(Struct));

                //Value type without constructor
                bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(I1) }, injector);
                Action action = () => bindingBuilder.To(typeof(StructEmpty));
                action.ShouldThrow<InvalidBindingException>();
            }
        }

        [TestMethod]
        public void ToConstantTest()
        {
            using (var injector = GetInjector())
            {
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(I1) }, injector);
                bindingBuilder.ToConstant(new Class());
                bindingBuilder.Build();
            }
        }

        [TestMethod]
        public void ToConstantNotAssignableTest()
        {
            using (var injector = GetInjector())
            {
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(I1) }, injector);
                Action action = () => bindingBuilder.ToConstant(new Class2());
                action.ShouldThrow<ArgumentException>();
            }
        }

        [TestMethod]
        public void ToConstantNullTest()
        {
            using (var injector = GetInjector())
            {
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(I1) }, injector);
                Action action = () => bindingBuilder.ToConstant(constValue: null);
                action.ShouldThrow<ArgumentNullException>();
            }
        }

        [TestMethod]
        public void ToConstantNotBindableOpenGenericClassTest()
        {
            using (var injector = GetInjector())
            {
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(I3<>) }, injector);
                Action action = () => bindingBuilder.ToConstant(new GenericClass2<string>());
                action.ShouldThrow<ArgumentException>();
            }
        }

        [TestMethod]
        public void ToMethodTest()
        {
            using (var injector = GetInjector())
            {
                //Assign
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(I1) }, injector);
                bindingBuilder.ToMethod(context => new Class());
                bindingBuilder.Build();

                //Null
                bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(I1) }, injector);
                Action action = () => bindingBuilder.ToMethod(null);
                action.ShouldThrow<ArgumentNullException>();
            }
        }

        [TestMethod]
        public void TestBindingBuilder()
        {
            using (var injector = GetInjector())
            {
                bool isBuilded = false;
                bool isBuilding = false;
                var bindingBuilder =
                    new BindingBuilder<I1, object, object, object>(new[] { typeof(I1) }, injector).To(typeof(Class));
                bindingBuilder.Building += builder =>
                                               {
                                                   isBuilded.ShouldBeFalse();
                                                   builder.ShouldNotBeNull();
                                                   isBuilding = true;
                                                   return builder;
                                               };
                bindingBuilder.Builded += builder =>
                                              {
                                                  var binding = builder.Build();
                                                  isBuilded = true;
                                                  isBuilding.ShouldBeTrue();
                                                  binding.ShouldBeType<TypeBinding>();
                                                  return binding;
                                              };

                /*bindingBuilder.Building += binding =>
                                               {
                                                   isBuilded.ShouldBeFalse();
                                                   binding.ShouldNotBeNull();
                                                   isBuilding = true;
                                               };

                bindingBuilder.Builded += binding =>
                                              {
                                                  isBuilded = true;
                                                  isBuilding.ShouldBeTrue();
                                                  binding.ShouldBeType<TypeBinding>();
                                              };*/
                bindingBuilder.Services.Contains(typeof(I1)).ShouldBeTrue();
                bindingBuilder.Injector.ShouldEqual(injector);

                var build = (TypeBinding)bindingBuilder.Build();
                build.ShouldNotBeNull();
                isBuilded.ShouldBeTrue();
            }
        }

        [TestMethod]
        public void DuplicateTypesTest()
        {
            using (var injector = GetInjector())
            {
                Action action = () =>
                                new BindingBuilder<object, object, object, object>(new[] { typeof(I1), typeof(I1) },
                                                                                   injector);
                action.ShouldThrow<ArgumentException>();
            }
        }

        [TestMethod]
        public void ToSelfTest()
        {
            using (var injector = GetInjector())
            {
                IBindingSyntax bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(Class) }, injector);
                bindingBuilder.ToSelf();
                bindingBuilder.Build().ShouldBeType<TypeBinding>();

                bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(Class), typeof(I1) }, injector);
                Action action = () => bindingBuilder.ToSelf();
                action.ShouldThrow<NotSupportedException>();
            }
        }

        [TestMethod]
        public void ManyTypesBindingTest()
        {
            using (var injector = GetInjector())
            {
                var correctClass = new[] { typeof(I1), typeof(A1), typeof(A2), typeof(A3), typeof(IDisposable) };
                var notCorrectClass = new[] { typeof(I1), typeof(A1), typeof(A2), typeof(A4), typeof(IDisposable) };

                IBindingSyntax bindingBuilder = new BindingBuilder<object, object, object, object>(correctClass, injector);
                Action action = () => new BindingBuilder<object, object, object, object>(notCorrectClass, injector);
                action.ShouldThrow<NotSupportedException>();
            }
        }
    }

    public class A1 { }

    public class A2 : A1 { }

    public class A3 : A2 { }

    public class A4 : A1 { }

    public interface I1
    {
    }

    public interface I2 : I1
    {
    }

    public interface I3<T> : I2
    {
    }

    public class Class : I1, I2
    {
    }

    public class Class2
    {
    }

    public struct Struct : I2
    {
        public Struct(string st)
        {
        }
    }

    public struct StructEmpty : I2
    {
    }

    public abstract class AbstractClass : I2
    {
    }

    public class GenericClass<T> : I2
    {
    }

    public class GenericClass2<T> : I3<T>
    {
    }
}